#include"Task.hpp"
#include<iostream>
#include<vector>
#include<string>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

const int processnum = 10;
std::vector<task_t> tasks;

// 先描述
class channel
{
public:
    channel(int cmdfd, pid_t slaverid, const std::string& processname)
        :_cmdfd(cmdfd)
        ,_slaverid(slaverid)
        ,_processname(processname)
    {}
public:
    int _cmdfd;               // 父进程发送任务的文件描述符
    pid_t _slaverid;          // 子进程的pid
    std::string _processname; // 子进程的名称，便于写日志
};

void slaver()
{
    while(true)
    {
        int cmdcode = 0;
        int n = read(0, &cmdcode, sizeof(int)); // 每次固定从0端读 4Byte大小的内容  n读到的是内容的字节大小
        if(n == sizeof(int))
        {
            std::cout<<"slaver say got a command : "<<getpid()<<" : cmdcode"<<cmdcode<<std::endl;
            if(cmdcode >= 0 && cmdcode < tasks.size()) // 条件合法，执行任务
                tasks[cmdcode]();
        }
        if(n == 0) break;
    }
}

void Menu()
{
    std::cout << "#################################################" << std::endl;
    std::cout << "#   1. 刷新日志             2. 刷新野怪         #" << std::endl;
    std::cout << "#   3. 检测软件更新         4. 更新血量和蓝量   #" << std::endl;
    std::cout << "#                0. 退出                        #" << std::endl;
    std::cout << "#################################################" << std::endl;
}

void InitProcessPool(std::vector<channel>* channels)
{
    std::vector<int> oldfds;
    for (int i = 0; i < processnum; i++)
    {
        
        // 建立管道
        int pipefd[2];
        int n = pipe(pipefd);
        assert(!n);
        (void)n; // n不会再用到

        // 建立进程
        pid_t id = fork();
        if(id == 0) // child
        {
            std::cout<<"child - "<<getpid()<<"close oldfd: ";
            // 关闭子进程的写端
            for(auto& fd : oldfds) 
            {
                std::cout<<fd<<" ";
                close(fd);
            }
            std::cout<<std::endl;
        
            close(pipefd[1]);   // child只读，关闭写端
            dup2(pipefd[0], 0); // 重定向，让子进程没有管道的概念，从系统标准读入就可以了 -- 不用给slaver传递rfd了
            close(pipefd[0]);   // 用完关闭
            slaver();

            // 进程退出
            std::cout<<"process : "<<getpid()<<"quit"<<std::endl;
            exit(0);
        }
        // father
        close(pipefd[0]);   // father只读，关闭写端

        // 添加channel字段
        std::string name = "process-" + std::to_string(i);
        channels->push_back(channel(pipefd[1], id, name));
        oldfds.push_back(pipefd[1]); // 写端保存，需要后续关闭

        sleep(1);
    }
    
}

void CtrlSlaver(const std::vector<channel>& channels)
{
    int which = 0;
    while(true)
    {
        int input = 0;
        // 1.选择任务
        Menu();
        std::cout<<"Please Enter@";
        std::cin>>input;
        if(input <= 0 || input >= 5) break;
        int cmdcode = input - 1;

        // 2.选择进程
        std::cout<<"father say: "<<
            "cmdcode: "<<cmdcode<<" already sendto "<<channels[which]._slaverid<<
            " process name: "<<channels[which]._processname<<std::endl;

        // 3.发送任务
        write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));

        ++which;
        which %= channels.size();
    }
}

void Quit(const std::vector<channel>& channels)
{
    // 问题：子进程继承了父进程的写端，如果不及时关闭写端，会导致进程阻塞

    // 解决3：随时关闭写端，确保每个子进程都有一个写端
    for(const auto& e : channels)
    {
        close(e._cmdfd);
        waitpid(e._slaverid, nullptr, 0);
    }

    // 解决2：倒叙关闭父进程文件描述符，接着等待子进程退出
    // int n = channels.size()-1;
    // for(int i = n; i >= 0; i--)
    // {
    //     close(channels[i]._cmdfd);
    //     waitpid(channels[i]._slaverid, nullptr, 0);
    // }
    // 解决1：先把所有的父进程文件描述符关闭，再等待子进程
    // // 关闭文件
    // for(const auto& e : channels)
    // {
    //     close(e._cmdfd);
    // }
    // // 父进程等待
    // for(const auto& e : channels)
    // {
    //     waitpid(e._slaverid, nullptr, 0);
    // }
}

int main()
{
    // 再组织
    std::vector<channel> channels;

    // 加载任务
    LoadTasks(&tasks);

    // 1.初始化
    InitProcessPool(&channels);

    // 2.控制子进程
    CtrlSlaver(channels);

    // 3.清理收尾
    Quit(channels);
}